﻿namespace FasterThanLightEngine.Math
{
    using System;
    using System.Security.AccessControl;

    /// <summary>
    /// A class reprisenting a 2D vector and encapsulating related operations.
    /// </summary>
    public class Vector2D
    {
        /// <summary>
        /// An enum used to describe the how the axes of the <see cref="Vector2D"/>
        /// map to a <see cref="Vector3D"/>.
        /// </summary>
        public enum VectorPromotionType
        {
            XtoXYtoY,
            XtoXYtoZ
        }

        /// <summary>
        /// Gets or sets the X component of the <see cref="Vector2D"/>.
        /// </summary>
        public double X
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the Y component of the <see cref="Vector2D"/>.
        /// </summary>
        public double Y
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the magnitude of the <see cref="Vector2D"/>.
        /// </summary>
        public double Magnitude
        {
            get
            {
                return Math.Sqrt(this.Magnitude2);
            }
        }

        /// <summary>
        /// Gets the square of the magnitude of the <see cref="Vector2D"/>.
        /// This is preferred as it avoids the overhead of calculating the
        /// Square Root of the magnitude. Square the value you are comparing
        /// against for comparison checks.
        /// </summary>
        public double Magnitude2
        {
            get
            {
                return this.X*this.X + this.Y*this.Y;
            }
        }

        /// <summary>
        /// Creates a default instance of a <see cref="Vector2D"/>.
        /// </summary>
        public Vector2D()
        { }

        /// <summary>
        /// Creates a new instance of the <see cref="Vector2D"/> with the given values.
        /// </summary>
        /// <param name="x">The X component of the <see cref="Vector2D"/>.</param>
        /// <param name="y">The Y component of the <see cref="Vector2D"/>.</param>
        public Vector2D(double x, double y)
        {
            this.X = x;
            this.Y = y;
        }

        /// <summary>
        /// Creates a new instance of the <see cref="Vector2D"/> class based off a current one.
        /// </summary>
        /// <param name="other">The current <see cref="Vector2D"/> that the new <see cref="Vector2D"/> will be based on.</param>
        public Vector2D(Vector2D other)
        {
            this.X = other.X;
            this.Y = other.Y;
        }

        /// <summary>
        /// Calculates the Dot Product of the current <see cref="Vector2D"/> and another <see cref="Vector2D"/>.
        /// </summary>
        /// <param name="other">The right operand for the calculation.</param>
        /// <returns>The scalar result of the Dot Product.</returns>
        public double DotProduct(Vector2D other)
        {
            if (other == null)
                throw new ArgumentException("Vector parameter for DotProduct must not be null.");

            return this.X * other.X + this.Y * other.Y;
        }

        /// <summary>
        /// Promotes a <see cref="Vector2D"/> into a <see cref="Vector3D"/> using the given mapping between the axes.
        /// </summary>
        /// <param name="conversionType">The mapping between source and destination axes.</param>
        /// <returns>A <see cref="Vector3D"/>.</returns>
        public Vector3D ToVector3D(VectorPromotionType conversionType = VectorPromotionType.XtoXYtoY)
        {
            var result = new Vector3D();

            switch (conversionType)
            {
                case VectorPromotionType.XtoXYtoY:
                    result = new Vector3D(this.X, this.Y, 0);
                    break;
                case VectorPromotionType.XtoXYtoZ:
                    result = new Vector3D(this.X, 0, this.Y);
                    break;
            }

            return result;
        }

        /// <summary>
        /// Provides a standard string reprisentation of the <see cref="Vector2D"/>.
        /// </summary>
        /// <returns>The string reprisentation of the <see cref="Vector2D"/>.</returns>
        public override string ToString()
        {
            return "(" + this.X + ", " + this.Y + ")";
        }

        /// <summary>
        /// Adds the given <see cref="Vector2D"/> to another <see cref="Vector2D"/>.
        /// </summary>
        /// <param name="left">The left operand.</param>
        /// <param name="right">The right operand.</param>
        /// <returns>The resultant <see cref="Vector2D"/>.</returns>
        public static Vector2D operator +(Vector2D left, Vector2D right)
        {
            if (left == null || right == null)
                throw new ArgumentException("Neither operator for Vector-Vector addition can be null.");

            return new Vector2D(left.X + right.X, left.Y + right.Y);
        }

        /// <summary>
        /// Subtracts the given <see cref="Vector2D"/> from another <see cref="Vector2D"/>.
        /// </summary>
        /// <param name="left">The left operand.</param>
        /// <param name="right">The right operand.</param>
        /// <returns>The resultant <see cref="Vector2D"/>.</returns>
        public static Vector2D operator -(Vector2D left, Vector2D right)
        {
            if (left == null || right == null)
                throw new ArgumentException("Neither operator for Vector-Vector subtraction can be null.");

            return new Vector2D(left.X - right.X, left.Y - right.Y);
        }

        /// <summary>
        /// Multiplies the given <see cref="Vector2D"/> by a scalar integer.
        /// </summary>
        /// <param name="left">The <see cref="Vector2D"/>.</param>
        /// <param name="right">The multiplier.</param>
        /// <returns>The result of the multiplication.</returns>
        public static Vector2D operator * (Vector2D left, double right)
        {
            return new Vector2D(left.X * right, left.Y * right);
        }

        /// <summary>
        /// Multiplies the given <see cref="Vector2D"/> by a scalar integer.
        /// </summary>
        /// <param name="left">The multiplier.</param>
        /// <param name="right">The <see cref="Vector2D"/>.</param>
        /// <returns>The result of the multiplication.</returns>
        public static Vector2D operator *(double left, Vector2D right)
        {
            return new Vector2D(left * right.X, left * right.Y);
        }

        /// <summary>
        /// Multiplies the two given <see cref="Vector2D"/> values together.
        /// </summary>
        /// <param name="left">The left operand.</param>
        /// <param name="right">The right operand.</param>
        /// <returns>The result of the multiplication.</returns>
        public static Vector2D operator *(Vector2D left, Vector2D right)
        {
            if (left == null || right == null)
                throw new ArgumentException("Neither operator for Vector-Vector multiplication can be null.");

            return new Vector2D(left.X * right.X, left.Y * right.Y);
        }

        /// <summary>
        /// Divides the given <see cref="Vector2D"/> with a scalar value.
        /// </summary>
        /// <param name="left">The <see cref="Vector2D"/> to divide.</param>
        /// <param name="right">The scalar value to divide by.</param>
        /// <returns>The result of the division.</returns>
        public static Vector2D operator /(Vector2D left, double right)
        {
            if (left == null)
                throw new ArgumentException("Left operand for Vector division must not be null.");

            return new Vector2D(left.X / right, left.Y / right);
        }
        
        // TODO: Add addition and subtraction operators for points. This is the primary way points should be advanced in the engine.
    }
}
